/**
 * Created with IntelliJ IDEA.
 * Description: 牛客网: CC19 分割回文串-ii
 * <a href="https://www.nowcoder.com/practice/1025ffc2939547e39e8a38a955de1dd3?tpId=46&tPage=1&rp=1&ru=%2Fta%2Fleetcode&qru=%2Fta%2Fleetcode%2Fquestion-ranking&difficulty=&judgeStatus=&tags=&title=&sourceUrl=&gioEnter=menu">...</a>
 * User: DELL
 * Date: 2023-05-09
 * Time: 23:43
 */
public class Solution {
    /**
     * 解法一
     * 时间复杂度: O(N^3)
     * 空间复杂度: O(N)
     * 以下以 [x,y] 表示 s 的第 x 个字符到第 y 个字符的字串
     * 这里主要考虑的是动态规划
     * 状态定义: dp[i] = [1,i]的最小切割数
     * 状态转移: 设 任意 a <= i
     *          当[a,i]是回文串: dp[i] = min(dp[i],dp[a-1]+1)
     *          当[a,i]不是回文串: dp[i] 不做改变
     * 初始化: dp[i] = i-1 (即最坏就是将每个字符都切割开)
     *        这里将 dp[0]设置为 -1 很关键,因为如果第 [1,i]为回文串,则
     *        dp[a-1]+1 即 dp[0]+1 应为 0,即dp[0]需要设置为 -1
     * 返回值: dp[s.length]
     * @param s
     * @return
     */
    public int minCut (String s) {
        //检验合法性
        if (s == null || s.length() == 0) {
            return -1;
        }
        //状态数组
        int[] dp = new int[s.length()+1];
        //初始化
        for (int i = 0; i <= s.length(); i++) {
            dp[i] = i-1;
        }
        //状态转移
        for (int i = 2; i <= s.length(); i++) {
            for (int a = 1; a <= i; a++) {
                if (isPal(s,a,i)) {
                    dp[i] = Math.min(dp[i],dp[a-1]+1);
                }
            }
        }
        return dp[s.length()];
    }

    /**
     * 判断 s 的从第left个字符到第right字符的字串是否为回文串(两边均为闭区间)
     * @param s
     * @param left
     * @param right
     * @return
     */
    private static boolean isPal(String s, int left, int right) {
        while (left < right) {
            if (s.charAt(left-1) != s.charAt(right-1)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

    /**
     * 解法二 (针对解法一的时间复杂度进行优化)
     * 时间复杂度 O(N^2)
     * 空间复杂度 O(N^2)
     * 解法一的时间复杂度为 O(N^3),实在太高了,因此我们必须进行优化
     * 对于解法一的状态定义和转移方程我们很难进行优化,因此这里我们对求是否是回文串进行优化
     * 即 先求出字符串 s 中所有的是回文串的字串,之后在进行状态转移的时候不用判定每个子串是否为回文串
     * 同时通过动态规划求出字符串 s 中所有的是回文串的字串的时间复杂度为 O(N^2),
     * 因此整体时间复杂度就从 O(N^3) 降到了 O(N^2)
     * 具体做法如下:
     * 以下以 [x,y] 表示 s 的第 x 个字符到第 y 个字符的字串
     * 状态定义: dp[i][j] = true 当且仅当 [i,j]是回文串 (i <= j)
     * 状态转移: 当 i+1 == j时: dp[i][j] = true 当且仅当 s 的第 i 个字符和第 j 个字符相等
     *         其余情况时: dp[i][j] = s.charAt(i) == s.charAt(j) && dp[i+1][j-1]
     * 初始化: 当 i == j时: dp[i][j] = true
     * 返回值: 返回这个 dp数组 即可
     *
     * 特别注意这个动态规划的状态转移过程,因为 i <= j 是前提条件,因此我们只需要遍历矩阵的右上三角即可,
     * 同时从转移方程中我们可以得出 dp[i][j] 和 dp[i+1][j-1]的值有关,即和左下角的值有关
     * 因此我们在进行状态转移的时候需要从下网上遍历
     *
     * @param s
     * @return
     */
    public int minCut2 (String s) {
        //检验合法性
        if (s == null || s.length() == 0) {
            return -1;
        }
        //得到标记回文串的二维数组
        boolean[][] mat = getMat(s);
        //状态数组
        int[] dp = new int[s.length()+1];
        //初始化
        for (int i = 0; i <= s.length(); i++) {
            dp[i] = i-1;
        }
        //状态转移
        for (int i = 2; i <= s.length(); i++) {
            for (int a = 1; a <= i; a++) {
                if (mat[a][i]) {
                    dp[i] = Math.min(dp[i],dp[a-1]+1);
                }
            }
        }
        return dp[s.length()];
    }
    
    private static boolean[][] getMat (String s) {
        //状态定义
        boolean[][] dp = new boolean[s.length()+1][s.length()+1];
        //状态初始化
        for (int i = 0; i <= s.length(); i++) {
            dp[i][i] = true;
        }
        //状态转移
        for (int i = s.length()-1; i >= 1; i--) {
            for (int j = s.length(); j > i; j--) {
                if (i + 1 == j) {
                    dp[i][j] = (s.charAt(i-1) == s.charAt(j-1));
                } else {
                    dp[i][j] = (s.charAt(i-1) == s.charAt(j-1)) && dp[i+1][j-1];
                }
            }
        }
        return dp;
    }
}